//
// Unihan Input Method
//
// Copyright © 2002-2007 Sun Wah Linux Ltd.
//
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
// USA
//
// $Id: context.c 757 2007-05-10 08:19:31Z roger $
//

/* FILE : context.c */
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include <stdio.h>
#include <string.h>

#include <unihan_im.h>
#include <unihan_im_keycodes.h>
#include <SWTable.h>

#include "im_config.h"
#include "candidates.h"
#include "context.h"
#include "table.h"
#include "shuangpin.h"
#include "punctuation.h"

static PreeditList *get_preedit_list(IMKeyEvent *kev, TableConfig *config);
static void free_preedit_list(PreeditList *preedit_list);
static void context_associate_nodes(Node *node1,
				Node *node2,
				BaseTable *base_table,
				Adjuster *adjuster,
				TableConfig *config);

static PreeditList *
get_preedit_list(IMKeyEvent *kev, TableConfig *config)
{
	PreeditList *preedit_list;
	char **preedit;

	if (config->SPMode) {
		
	}

	preedit_list = (PreeditList *)malloc(sizeof(PreeditList));
	memset(preedit_list, 0, sizeof(PreeditList));

	char *string = (char *)malloc(2);
	string[0] = kev->keyChar;
	string[1] = 0x0;

	preedit = (char **)malloc(sizeof(char *));
	preedit[0] = string;

	preedit_list->preedit = preedit;
	preedit_list->num = 1;
	return NULL;
}

static void
free_preedit_list(PreeditList *preedit_list)
{
	int i;

	for(i=0; i<preedit_list->num; i++) {
		free(preedit_list->preedit[i]);
	}
	free(preedit_list->preedit);
}

void
context_init(Context *context)
{
#if DEBUG
	fprintf(stderr, "table : context init\n");
#endif
	return;
}

void
context_done(Context *context)
{
	context->currNode = context->sentence;

	while (context->sentence) {
		context->sentence = context->sentence->next;
		node_destroy(context->currNode);
		free(context->currNode);
		context->currNode = context->sentence;
	}

	context->sentence = context->currNode = context->lastNode = context->achorNode = NULL;
	context->lenSentence = 0;
	context->cursor = 0;
}

int
context_key_stacked(Context *context,
		IMKeyEvent *kev,
		TableConfig *config,
		BaseTable *base_table,
		Adjuster *adjuster,
		PhraseTableList *phrase_table_list,
		int num_phrase_table)
{
#if DEBUG
	fprintf(stderr, "table : context key stacked\n");
#endif
	if (kev->keyCode == IM_VK_BACK_SPACE) {
		if (context->currNode) {
			if (context->currNode->kicked) {
				Node *prev = context->currNode->prev;
				Node *next = context->currNode->next;

				if (context->achorNode == context->currNode) {
					while (context->achorNode) {
						if (context->achorNode->achor == PhraseStart ||
							context->achorNode->achor == PhraseEnd)
							break;
						context->achorNode = context->achorNode->prev;
					}
				}

				node_destroy(context->currNode);
				free(context->currNode);
				context->currNode = prev;
				if (prev)
					prev->next = next;
				else
					context->sentence = next;
				if (next)
					next->prev = prev;
				else
					context->lastNode = prev;
				context->cursor--;
				context->lenSentence--;
				return TRUE;
			}
			node_pop(context->currNode,
					config,
					base_table,
					adjuster,
					phrase_table_list);
			if (!context->currNode->numTotalCands) {
				Node *prev = context->currNode->prev;
				Node *next = context->currNode->next;

				if (context->achorNode == context->currNode) {
					while (context->achorNode) {
						if (context->achorNode->achor == PhraseStart ||
							context->achorNode->achor == PhraseEnd)
							break;
						context->achorNode = context->achorNode->prev;
					}
				}

				node_destroy(context->currNode);
				free(context->currNode);
				context->currNode = prev;
				if (prev)
					prev->next = next;
				else
					context->sentence = next;
				if (next)
					next->prev = prev;
				else
					context->lastNode = prev;
				context->cursor--;
				context->lenSentence--;
			}
			return TRUE;
		} else
			return FALSE;
	}
	if (context->currNode) {
		if (context->currNode->kicked) {
			Node *next_node = context->currNode->next;
			Node *new_node;
			new_node = (Node *)malloc(sizeof(Node));
			memset(new_node, 0, sizeof(Node));

			context->currNode->next = new_node;
			new_node->prev = context->currNode;
			new_node->next = next_node;
			if (next_node)
				next_node->prev = new_node;
			context->currNode = new_node;
			if (!new_node->next)
				context->lastNode = new_node;
			context->cursor++;
			context->lenSentence++;
		}
	} else {
		context->currNode = (Node *)malloc(sizeof(Node));
		memset(context->currNode, 0, sizeof(Node));

		context->currNode->next = context->sentence;
		if (context->sentence)
			context->sentence->prev = context->currNode;
		context->sentence = context->currNode;
		if (!context->currNode->next)
			context->lastNode = context->currNode;
		context->cursor++;
		context->lenSentence++;
	}

	if (node_stacked(context->currNode,
			kev,
			config,
			base_table,
			adjuster,
			phrase_table_list)) {
#if DEBUG
		fprintf(stderr, "table : node stacked SUCCESS\n");
#endif
		if (context->achorNode && context->achorNode->achor == PhraseStart) {
			char *preedit;
			int preedit_len = 0;
			int num_s_node = 1;
			SNode *s_node = NULL;
			int i,j;
			Node *tmp_node;

			tmp_node = context->achorNode;
			do {
				s_node = (SNode *)realloc(s_node,
						sizeof(SNode)*num_s_node*tmp_node->numSNode);
				if (tmp_node == context->achorNode)
					memset(s_node, 0, sizeof(SNode)*num_s_node*tmp_node->numSNode);
				for (i=0; i<tmp_node->numSNode; i++) {
					if (s_node) {
						memcpy(&s_node[i*num_s_node],
							s_node,
							num_s_node);
						s_node->numCands = 0;
					}
					for(j=0; j<num_s_node; j++) {
						strcat(s_node[i*num_s_node+j].preedit,
							tmp_node->sNode[j].preedit);
						s_node[i*num_s_node+j].preeditLen +=
							tmp_node->sNode[j].preeditLen;
					}
				}
				num_s_node *= tmp_node->numSNode;
			} while (tmp_node != context->currNode, tmp_node=tmp_node->next);
			if (snode_phrase_search(s_node,
					num_s_node,
					base_table,
					phrase_table_list,
					num_phrase_table)) {
				/* node select candidates */
				int idx = 0;
				tmp_node = context->currNode->prev;
				do {
					node_select_cand_from_snodes(tmp_node,
							config,
							base_table,
							phrase_table_list,
							num_phrase_table,
							s_node,
							num_s_node,
							idx++);
				} while (tmp_node != context->currNode, tmp_node=tmp_node->next);
				/*if (node_is_last_in_phrase(context->currNode,
							phrase_table_list,
							num_phrase_table,
							s_node,
							num_s_node))
					context->currNode->achor = PhraseEnd;*/
			} else {
				if (context->currNode->prev->achor == PhraseStart)
					context->currNode->prev->achor = PhraseNone;
				else
					context->currNode->prev->achor = PhraseEnd;
				context->achorNode = context->currNode->prev;
				int check_list_index;
				CN_IDX cn_idx;
				UTF_CN_TO_INT_IDX(cn_idx, node_get_selected_candidates(context->currNode->prev,
								base_table,
								phrase_table_list));
				check_list_index = adjuster->stickTable[
						base_table->checkListIndex[
						base_table->rsTable[cn_idx-
						base_table->header->minChar]
						].stickOff
						].suffixOff[0];
				if (check_list_index)
					node_set_first_cand(context->currNode,
							check_list_index,
							base_table,
							config);
			}
			if (s_node) {
				for (i=0; i<num_s_node; i++)
					free(s_node[i].candidates);
				free(s_node);
			}
		} else {
			if (context->currNode->prev && !context->currNode->prev->punc) {
				int check_list_index;
				CN_IDX cn_idx;
				UTF_CN_TO_INT_IDX(cn_idx, node_get_selected_candidates(context->currNode->prev,
								base_table,
								phrase_table_list));
				check_list_index = adjuster->stickTable[
						base_table->checkListIndex[
						base_table->rsTable[cn_idx-
						base_table->header->minChar]
						].stickOff
						].suffixOff[0];
				if (check_list_index)
					node_set_first_cand(context->currNode,
							check_list_index,
							base_table,
							config);
			}
		}
		return TRUE;
	} else {
#if DEBUG
		fprintf(stderr, "table : node stacked FAILED\n");
#endif
		Node *next_node = context->currNode->next;
		Node *new_node;

		new_node = (Node *)malloc(sizeof(Node));
		memset(new_node, 0, sizeof(Node));
		if (node_stacked(new_node,
				kev,
				config,
				base_table,
				adjuster,
				phrase_table_list)) {
			node_kick(context->currNode, kev, config);
			if (context->currNode->achor == PhraseEnd ||
				context->currNode->achor == PhraseStart) {
				context->achorNode = context->currNode;
			}
			context->currNode->next = new_node;
			new_node->prev = context->currNode;
			new_node->next = next_node;
			if (next_node)
				next_node->prev = new_node;
			context->currNode = new_node;
			if (!new_node->next)
				context->lastNode = new_node;
			context->cursor++;
			context->lenSentence++;

			/* Search Phrase Tables */
			PhraseAchor achor = context->achorNode?context->achorNode->achor:PhraseEnd;
			int preedit_len = 0;
			int num_s_node = 1;
			SNode *s_node = NULL;
			int i,j;
			Node *tmp_node;

			switch (achor) {
			case PhraseStart:
				tmp_node = context->achorNode;
				do {
					s_node = (SNode *)realloc(s_node,
							sizeof(SNode)*num_s_node*tmp_node->numSNode);
					if (tmp_node == context->achorNode)
						memset(s_node, 0, sizeof(SNode)*num_s_node*tmp_node->numSNode);
					for (i=0; i<tmp_node->numSNode; i++) {
						if (s_node) {
							memcpy(&s_node[i*num_s_node],
								s_node,
								num_s_node);
							s_node->numCands = 0;
						}
						for(j=0; j<num_s_node; j++) {
							strcat(s_node[i*num_s_node+j].preedit,
								tmp_node->sNode[j].preedit);
							s_node[i*num_s_node+j].preeditLen +=
								tmp_node->sNode[j].preeditLen;
						}
					}
					num_s_node *= tmp_node->numSNode;
				} while (tmp_node != context->currNode, tmp_node=tmp_node->next);
				if (snode_phrase_search(s_node,
						num_s_node,
						base_table,
						phrase_table_list,
						num_phrase_table)) {
					/* node select candidates */
					int idx = 0;
					tmp_node = context->currNode->prev;
					do {
						node_select_cand_from_snodes(tmp_node,
								config,
								base_table,
								phrase_table_list,
								num_phrase_table,
								s_node,
								num_s_node,
								idx++);
					} while (tmp_node != context->currNode, tmp_node=tmp_node->next);
					/*if (node_is_last_in_phrase(context->currNode,
								phrase_table_list,
								num_phrase_table,
								s_node,
								num_s_node))
						context->currNode->achor = PhraseEnd;*/
				} else {
					if (context->currNode->prev->achor == PhraseStart)
						context->currNode->prev->achor = PhraseNone;
					else
						context->currNode->prev->achor = PhraseEnd;
					context->achorNode = context->currNode->prev;
					int check_list_index;
					CN_IDX cn_idx;
					UTF_CN_TO_INT_IDX(cn_idx, node_get_selected_candidates(
								context->currNode->prev,
								base_table,
								phrase_table_list));
					check_list_index = adjuster->stickTable[
							base_table->checkListIndex[
							base_table->rsTable[cn_idx-
							base_table->header->minChar]].stickOff
							].suffixOff[0];
					if (check_list_index)
						node_set_first_cand(context->currNode,
								check_list_index,
								base_table,
								config);
				}
				break;
			case PhraseEnd:
			case PhraseNone:
			default:
				/* if phrase search success, 		*/
				/* set prev Node achor to PhraseStart	*/
				tmp_node = context->currNode->prev;
				do {
					s_node = (SNode *)realloc(s_node,
							sizeof(SNode)*num_s_node*tmp_node->numSNode);
					if (tmp_node == context->currNode->prev)
						memset(s_node, 0, sizeof(SNode)*num_s_node*tmp_node->numSNode);

					for (i=0; i<tmp_node->numSNode; i++) {
						if (s_node && tmp_node->numSNode>1) {
							memcpy(&s_node[i*num_s_node],
								s_node,
								num_s_node);
							//s_node->numCands = 0;
						}
						for(j=0; j<num_s_node; j++) {
							strcat(s_node[i*num_s_node+j].preedit,
								tmp_node->sNode[j].preedit);
							s_node[i*num_s_node+j].preeditLen +=
								tmp_node->sNode[j].preeditLen;
						}
					}
					num_s_node *= tmp_node->numSNode;
				} while (tmp_node != context->currNode, tmp_node=tmp_node->next);

				if (snode_phrase_search(s_node,
						num_s_node,
						base_table,
						phrase_table_list,
						num_phrase_table)) {
					/* node select candidates */
					int idx = 0;
					tmp_node = context->currNode->prev;
					do {
						node_select_cand_from_snodes(tmp_node,
								config,
								base_table,
								phrase_table_list,
								num_phrase_table,
								s_node,
								num_s_node,
								idx++);
					} while (tmp_node != context->currNode, tmp_node=tmp_node->next);
					context->currNode->prev->achor = PhraseStart;
					context->achorNode = context->currNode->prev;
					/*if (node_is_last_in_phrase(context->currNode,
								phrase_table_list,
								num_phrase_table,
								s_node,
								num_s_node))
						context->currNode->achor = PhraseEnd;*/
				} else {
					int check_list_index;
					CN_IDX cn_idx;
					UTF_CN_TO_INT_IDX(cn_idx, node_get_selected_candidates(
								context->currNode->prev,
								base_table,
								phrase_table_list));
					check_list_index = adjuster->stickTable[
							base_table->checkListIndex[
							base_table->rsTable[cn_idx-
							base_table->header->minChar]
							].stickOff
							].suffixOff[0];
					if (check_list_index)
						node_set_first_cand(context->currNode,
								check_list_index,
								base_table,
								config);
				}
				break;
			}
			if (s_node) {
				for (i=0; i<num_s_node; i++)
					free(s_node[i].candidates);
				free(s_node);
			}
			return TRUE;
		} else {
			node_pop(new_node,
					config,
					base_table,
					adjuster,
					phrase_table_list);
			node_destroy(new_node);
			free(new_node);
			return FALSE;
		}

	}
}

int
context_punctuation_stacked(Context *context,
			IMKeyEvent *kev,
			TableConfig *config)
{
#if DEBUG
	fprintf(stderr, "table : context punctuation stacked\n");
#endif
	Node *new_node;
	Node *next;
	const char *punc = NULL;

	if (config->FullShape) {
		punc = get_fw_punctuation(kev->keyChar);
	}

	if (config->PuncMode) {
		punc = get_cn_punctuation(kev->keyChar);
	}

	if (!punc)
		return FALSE;

	new_node = (Node *)malloc(sizeof(Node));
	memset(new_node, 0, sizeof(Node));

	new_node->punc = 1;
	new_node->puncString = punc;
	new_node->defaultCandLen = strlen(punc);
	new_node->kicked = 1;
	new_node->editing = 1;

	if (context->currNode) {
		if (context->achorNode && context->achorNode->achor == PhraseStart) {
			context->currNode->achor = PhraseEnd;
			context->achorNode = context->currNode;
		}
		next = context->currNode->next;

		context->currNode->next = new_node;

		new_node->prev = context->currNode;
		new_node->next = next;

		if (next)
			next->prev = new_node;
		else
			context->lastNode = new_node;

		context->currNode = new_node;
	} else {
		new_node->next = context->sentence;

		if (context->sentence)
			context->sentence->prev = new_node;

		context->currNode = new_node;
		context->sentence = new_node;
		if (!context->currNode->next)
			context->lastNode = context->currNode;
	}

	context->cursor++;
	context->lenSentence++;

	return TRUE;
}
int
context_kick(Context *context,
		IMKeyEvent *kev,
		TableConfig *config,
		BaseTable *base_table,
		Adjuster *adjuster,
		PhraseTableList *phrase_table_list)
{
	if (node_kick(context->currNode, kev, config)) {
		if (context->currNode->prev) {
			context_associate_nodes(context->currNode->prev,
					context->currNode,
					base_table,
					adjuster,
					config);
		}
		return TRUE;
	} else {
		return FALSE;
	}
}

int
context_is_kicked(Context *context)
{
	if (context->currNode)
		return context->currNode->kicked;
	else /* when cursor == 0 */
		return TRUE;
}

int
context_page_up(Context *context,
		TableConfig *config,
		BaseTable *base_table,
		Adjuster *adjuster,
		PhraseTableList *phrase_table_list)
{
	if (!context->currNode)
		return FALSE;
	return node_page_up(context->currNode, config);
}

int
context_page_down(Context *context,
		TableConfig *config,
		BaseTable *base_table,
		Adjuster *adjuster,
		PhraseTableList *phrase_table_list)
{
	if (!context->currNode)
		return FALSE;
	return node_page_down(context->currNode, config);
}

int
context_sel_up(Context *context,
		TableConfig *config,
		BaseTable *base_table,
		Adjuster *adjuster,
		PhraseTableList *phrase_table_list)
{
	if (!context->currNode)
		return FALSE;
	return node_sel_up(context->currNode, config);
}

int
context_sel_down(Context *context,
		TableConfig *config,
		BaseTable *base_table,
		Adjuster *adjuster,
		PhraseTableList *phrase_table_list)
{
	if (!context->currNode)
		return FALSE;
	return node_sel_down(context->currNode, config);
}

int
context_move_cursor_before(Context *context,
		TableConfig *config,
		BaseTable *base_table,
		Adjuster *adjuster,
		PhraseTableList *phrase_table_list)
{
#if DEBUG
	fprintf(stderr, "table : context move cursor before\n");
#endif
	if (!context->cursor)
		return FALSE;
	if (context->currNode && !context->currNode->kicked)
		return FALSE;

	context->cursor--;
	context->currNode = context->currNode->prev;

	context->achorNode = context->currNode;
	while(context->achorNode && context->achorNode == PhraseNone) {
		context->achorNode = context->achorNode->prev;
	}
	return TRUE;
}

int
context_move_cursor_after(Context *context,
		TableConfig *config,
		BaseTable *base_table,
		Adjuster *adjuster,
		PhraseTableList *phrase_table_list)
{
	/* if cursor == 1, it means cursor is between the 1st node and the
	 * second node, and if cursor == lenSentence, the cursor is after
	 * the last node */
#if DEBUG
	fprintf(stderr, "table : context move cursor after\n");
#endif
	if (context->cursor == context->lenSentence)
		return FALSE;
	if (context->currNode && !context->currNode->kicked)
		return FALSE;

	context->cursor++;
	if (context->currNode)
		context->currNode = context->currNode->next;
	else
		context->currNode = context->sentence;
	
	if (context->currNode->achor != PhraseNone) {
		context->achorNode = context->currNode;
	}
	return TRUE;
}

int
context_move_cursor_first(Context *context,
		TableConfig *config,
		BaseTable *base_table,
		Adjuster *adjuster,
		PhraseTableList *phrase_table_list)
{
#if DEBUG
	fprintf(stderr, "table : context move cursor first\n");
#endif
	if (!context->cursor)
		return FALSE;
	if (context->currNode && !context->currNode->kicked)
		return FALSE;
	
	context->cursor = 0;
	context->currNode = NULL; /* located before the 1st node */
	context->achorNode = NULL;
	return TRUE;
}

int
context_move_cursor_last(Context *context,
		TableConfig *config,
		BaseTable *base_table,
		Adjuster *adjuster,
		PhraseTableList *phrase_table_list)
{
#if DEBUG
	fprintf(stderr, "table : context move cursor last\n");
#endif
	if (context->cursor == context->lenSentence)
		return FALSE;
	if (context->currNode && !context->currNode->kicked)
		return FALSE;
	context->cursor = context->lenSentence;
	context->currNode = context->lastNode;

	context->achorNode = context->currNode;
	while (context->achorNode && context->achorNode->achor != PhraseNone) {
		context->achorNode = context->achorNode->prev;
	}
	return TRUE;
}

static void
context_associate_nodes(Node *node1,
			Node *node2,
			BaseTable *base_table,
			Adjuster *adjuster,
			TableConfig *config)
{
#if DEBUG
	fprintf(stderr, "table : context associate nodes\n");
#endif
	int exist = 0;
	int i;
	int check_list_index1, check_list_index2;
	int stick_off1, stick_off2;

	if (node1->punc || node2->punc)
		return;

	check_list_index1 = node1->sNode[0].candidates[0].lowIdx +
			node1->page*config->pageSize +
			node1->luCurrCandOffset;
	check_list_index2 = node2->sNode[0].candidates[0].lowIdx +
			node2->page*config->pageSize +
			node2->luCurrCandOffset;
	if (check_list_index1 == check_list_index2)
		return;
	
	stick_off1 = base_table->checkListIndex[check_list_index1].stickOff;
	stick_off2 = base_table->checkListIndex[check_list_index2].stickOff;

	for (i=STICK_SIZE-1; i>=0; i--) {
		if (adjuster->stickTable[stick_off1].suffixOff[i] == check_list_index2) {
			exist = 1;
			if (i>0) {
				int tmp;
				tmp = adjuster->stickTable[stick_off1].suffixOff[i];
				adjuster->stickTable[stick_off1].suffixOff[i] = 
						adjuster->stickTable[stick_off1].suffixOff[i-1];
				adjuster->stickTable[stick_off1].suffixOff[i-1] = tmp;
			}
			break;
		}
	}
	if (!exist) {
		for (i=0; i<STICK_SIZE; i++) {
			if (adjuster->stickTable[stick_off1].suffixOff[i] < 0) {
				adjuster->stickTable[stick_off1].suffixOff[i] = check_list_index2;
				break;
			} else if (i == STICK_SIZE - 1) {
				adjuster->stickTable[stick_off1].suffixOff[i] = check_list_index2;
			}
		}
	}

	exist = 0;
	for (i=STICK_SIZE-1; i>=0; i--) {
		if (check_list_index1 == adjuster->stickTable[stick_off2].prefixOff[i]) {
			exist = 1;
			if (i>0) {
				int tmp;
				tmp = adjuster->stickTable[stick_off2].prefixOff[i];
				adjuster->stickTable[stick_off2].prefixOff[i] =
						adjuster->stickTable[stick_off2].prefixOff[i-1];
				adjuster->stickTable[stick_off2].prefixOff[i-1] = tmp;
			}
			break;
		}
	}
	if (!exist) {
		for (i=0; i<STICK_SIZE; i++) {
			if (adjuster->stickTable[stick_off2].prefixOff[i] < 0) {
				adjuster->stickTable[stick_off2].prefixOff[i] = check_list_index1;
				break;
			} else if (i == STICK_SIZE - 1) {
				adjuster->stickTable[stick_off2].prefixOff[i] = check_list_index1;
			}
		}
	}
}

// vi:ts=4:nowrap:cin:expandtab
